Este conjunto de datos consiste en una colección de características que mide forma y textura extraídas de imágenes digitales de especímenes de hojas procedentes de un total de 40 especies de plantas diferentes. Fue recolectado en Febrero del 2014 en la Facultad de Ciencias, Universidade do Porto, Portugal. https://archive.ics.uci.edu/ml/datasets/leaf
A continuacion algunas de estas especies:
from IPython.display import Image
Image("plants.png", width=650, height=650)
from IPython.display import Image
Image("RGB/40. Fragaria vesca/iPAD2_C40_EX01.JPG", width=350, height=350)
Image("RGB/15. Populus alba/iPAD2_C15_EX06.JPG", width=350, height=350)## Especie 40: Fragaria Vesca
Image("description.png", width=550, height=550)
Habitualmente la data se representa mediante tablas, donde cada columna es una característica de la data y cada fila es un registro.
Cada característica es una medición distinta de las demas. Y digamos, por ejemplo, que cada registro representa los datos medidos en un tiempo determinado, por ejemplo, la primera fila a la 1:00pm, la segunda 1:10pm, la tercera 1:15pm, etc.
Pasos:
import pandas as pd #1
names = ['species', 'specimen_number', 'eccentricity', 'aspect_ratio',
'elongation', 'solidity', 'stochastic_convexity', 'isoperimetric_factor',
'maximal_indentation_depth', 'lobedness', 'average_intensity',
'average_contrast', 'smoothness', 'third_moment', 'uniformity', 'entropy']
leaf = pd.read_csv("leaf.csv", names=names) #2
print('Filas y columnas', leaf.shape) #3
leaf.head(10) #4
Veamos algunos de sus características (columnas) :
'eccentricity', 'aspect_ratio', 'elongation', 'solidity'
Usaremos distintos gráficos para analizar las distribución de cada una de las columnas.
Los histogramas son gráficos en forma de barras, que muestran la frecuencia de un determinado intervalo de valores, es usado para variables continuas.
Image("explain_histograms.png", width=650, height=650)
%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
subset = ['eccentricity', 'aspect_ratio', 'elongation', 'solidity']
hist = leaf[subset] \
.plot(kind='hist', figsize=(5, 5), subplots=False, sharex=False)
_ = plt.xlabel("Values")
Se observa que no se logra visualizar correctamente todas las distribuciones, por tanto, se grafica por separado.
hist = leaf.eccentricity \
.plot(kind='hist', figsize=(5, 5), subplots=False, sharex=False)
_ = plt.xlabel("Values")
hist = leaf.aspect_ratio \
.plot(kind='hist', figsize=(5, 5), subplots=False, sharex=False)
_ = plt.xlabel("Values")
hist = leaf.elongation \
.plot(kind='hist', figsize=(5, 5), subplots=False, sharex=False)
_ = plt.xlabel("Values")
hist = leaf.solidity \
.plot(kind='hist', figsize=(5, 5), subplots=False, sharex=False)
_ = plt.xlabel("Values")
_ = leaf.plot(kind='density', figsize=(10, 10), subplots=True, sharex=False)
Sirven también para ver la distribución de los datos, pero su utilidad se enfoca en ver si hay valores atípicos o outliers, ver la dispersión de los valores y analizar los datos del histograma en una sola dimensión.
Image("explain_boxplots.png", width=550, height=550)
Image("explain_boxplots2.png", width=350, height=350)
_ = leaf.plot(kind='box',figsize=(10, 10), vert=False)
_ = plt.xlabel("Values")
plt.figure(figsize=(10,10))
_ = sns.boxplot(x="species", y="eccentricity", data=leaf)
_ = sns.stripplot(x="species", y="eccentricity", data=leaf, jitter=True, edgecolor="gray", size=5)
plt.figure(figsize=(10,10))
_ = sns.boxplot(x="species", y="aspect_ratio", data=leaf)
_ = sns.stripplot(x="species", y="aspect_ratio", data=leaf, jitter=True, edgecolor="gray")
plt.figure(figsize=(10,10))
_ = sns.boxplot(x="species", y="elongation", data=leaf)
_ = sns.stripplot(x="species", y="elongation", data=leaf, jitter=True, edgecolor="gray")
Tal como se vio anteriormente, graficar todos los histogramas para cada una de las clases puede dar lugar a un solapamiento de distribuciones, por tanto, tomaremos las primeras 50 filas por un tema de claridad.
_ = sns.FacetGrid(leaf.head(50), hue="species", size=6) \
.map(sns.distplot, "eccentricity") \
.add_legend()
_ = sns.FacetGrid(leaf.head(50), hue="species", size=6) \
.map(sns.distplot, "aspect_ratio") \
.add_legend()
_ = sns.FacetGrid(leaf.head(50), hue="species", size=6) \
.map(sns.distplot, "elongation") \
.add_legend()
Con los siguientes gráficos podemos observar cúmulos o grupos definidos, donde cada grupo es una clase. Por tanto, el problema trata de clasificación, especificamente, clasificación múltiple supervisada.
leaf_subset = leaf[subset + ['species']].head(50)
leaf_subset.head()
_ = sns.pairplot(leaf_subset, hue='species', size=2)
Es una manera de expresar un dataset, de dimensión alta, en grupos fácilmente identificables.
from pandas.tools.plotting import andrews_curves
plt.figure(figsize=(10,10))
_ = andrews_curves(leaf_subset, "species")
specie_1 = leaf_subset.query("species == 1")
specie_2 = leaf_subset.query("species == 2")
specie_3 = leaf_subset.query("species == 3")
specie_4 = leaf_subset.query("species == 4")
f, ax = plt.subplots(figsize=(12, 12))
# ax.set_aspect("equal")
ax = sns.kdeplot(specie_1.solidity, specie_1.elongation,
cmap="Blues", shade=True, shade_lowest=False)
ax = sns.kdeplot(specie_2.solidity, specie_2.elongation,
cmap="Reds", shade=True, shade_lowest=False)
ax = sns.kdeplot(specie_3.solidity, specie_3.elongation,
cmap="Greens", shade=True, shade_lowest=False)
ax = sns.kdeplot(specie_4.solidity, specie_4.elongation,
cmap="Oranges", shade=True, shade_lowest=False)
blue = sns.color_palette("Blues")[-2]
red = sns.color_palette("Reds")[-2]
green = sns.color_palette("Greens")[-2]
orange = sns.color_palette("Oranges")[-2]
_ = ax.text(0.94, 0.45, "specie 1: Quercus suber", size=16, color=blue)
_ = ax.text(0.94, 0.55, "specie 2: Salix atrocinera", size=16, color=red)
_ = ax.text(0.90, 0.25, "specie 3: Populus nigra", size=16, color=green)
_ = ax.text(0.96, 0.08, "specie 4: Alnus sp.", size=16, color=orange)
Muestra la correlación (directa o inversa) que existen entre las características o columnas, excepto la variable de respuesta. Esto sirve para ver si es posible resumir columnas (reducir), aumentar o combinarlas con el objetivo de reducir el tiempo de entrenamiento, eliminar redundancia de datos y mejorar el performance del modelo.
%%time
from collections import OrderedDict
class_name = 'species'
features = leaf.ix[:, leaf.columns != class_name]
dictFI = OrderedDict(features)
features = dictFI.keys()
features.append('species')
def plot_heatmap(df):
fig, axes = plt.subplots(figsize=(10,10))
_ = sns.heatmap(df, annot=True)
plot_heatmap(leaf[features].corr(method='pearson'))
Verificamos la cantidad de valores NaN : Not a Number
from __future__ import division
print 'filas sin valores nan: ', len(leaf.species.dropna())
print 'filas totales: ', len(leaf.species)
print(leaf.groupby('species').size())
Feature Selection es un proceso donde se selecciona automáticamente las características que contribuyen en mayor medida a la variable a predecir.
Reduces Overfitting: El modelo funcionará bien con data nunca vista.
Improves Accuracy: Datos menos engañosos significa una mejora en la exactitud del modelo.
Reduces Training Time: Menos data significa que los algoritmos entrenarán más rapido.
Selecciona las columnas que tengan una relación fuerte con la variable a predecir.
Para regresión: f_regression
Para classificación: chi2 or f_classif
En este caso de clasificación, se realiza el test chi square, el cual mide la dependencia entre variables estocásticas, este test remueve las características más probables a ser independientes, por lo tanto, irrelevantes para la clasificación.
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
class_name = 'species'
X = leaf.ix[:, leaf.columns != class_name]
X = leaf.ix[:, leaf.columns != 'specimen_number']
y = leaf[class_name]
test = SelectKBest(score_func=chi2, k=4)
fit = test.fit(X, y)
print fit.scores_
print
X.head()
Los Bagged decision trees como lo son Random Forest y Extra Trees pueden ser usados para estimar la importancia de caracteristicas. Asi como tambien, el algoritmo Gradient Boosting.
from sklearn.model_selection import cross_val_score
def modelfit(alg, dtrain, predictors, performCV=True, printFeatureImportance=True, cv_folds=5):
alg.fit(dtrain[predictors], dtrain['species'])
dtrain_predictions = alg.predict(dtrain[predictors])
dtrain_predprob = alg.predict_proba(dtrain[predictors])[:,1]
if printFeatureImportance:
feat_imp = pd.Series(alg.feature_importances_, predictors).sort_values(ascending=False)
feat_imp.plot(kind='bar', title='Feature Importances', figsize=(14.,7.))
plt.ylabel('Feature Importance Score')
plt.show()
plt.close()
return feat_imp
%%time
from sklearn.ensemble import ExtraTreesClassifier
target = ['species', 'specimen_number']
predictors = [x for x in leaf if x not in target]
model = ExtraTreesClassifier(random_state=10)
fi = modelfit(model, leaf, predictors)
print fi.head(6)
%%time
from sklearn.ensemble import GradientBoostingClassifier
target = ['species', 'specimen_number']
predictors = [x for x in leaf if x not in target]
model = GradientBoostingClassifier(random_state=10)
fi = modelfit(model, leaf, predictors)
print fi.head(6)
En este apartado se aprenderá cómo evaluar un algoritmo, mediante diferentes técnicas de estimacion:
Esta técnica es usada para grandes datasets, donde el entrenamiento tenga un coste alto
en recursos y tiempo. Por contra, esta técnica puede tener inducir a una alta varianza, es decir, las diferencias entre la data de entrenamiento (training) y de prueba (testing) pueden ser significativas a la hora de estimar la precisión del modelo.
Lo más comun es dividir la data en: 67% para el training y 33% para el testing.
Image("split_train_test.png", width=450, height=450)
%%time
from sklearn.linear_model import LogisticRegression
from sklearn import model_selection
test_size = 0.33
seed = 7
X_train, X_test, y_train, y_test = model_selection.train_test_split(X, y,
test_size=test_size, random_state=seed)
model = LogisticRegression()
model.fit(X_train, y_train)
result = model.score(X_test, y_test)
print("Accuracy: %.3f%%") % (result*100.0)
El seed o semilla es fijo para tener el mismo conjunto de datos en todo el proceso, de tal manera, que cuando se haga pruebas usando otro algoritmo, se pueda usar la misma dataset.
Esta técnica divide la data en k conjuntos, de tal manera que se entrena con los k-1 folds(pliegues) y se testea con el sobrante, luego se hace el mismo procedimiento escogiendo otros k-1 . Este procedimiento se realiza hasta que cada fold es testeado. Luego de tener las k puntuaciones de rendimiento se puede resumir usando la media o la desviación estandar. Este resultado es más fiable en el rendimiento del algoritmo cuando se presenta una nueva data. Es necesario que la elección de k permita que las muestras sean lo suficientemente grande, lo valores más comúnes para k son 3, 5, 10.
Image("K-fold_cross_validation.jpg", width=650, height=650)
%%time
from sklearn.cross_validation import KFold
from sklearn.model_selection import cross_val_score
num_folds = 10
num_instances = len(X)
seed = 7
# scoring = 'roc_auc'
kfold = KFold(n=num_instances, n_folds=num_folds,
random_state=seed)
model = LogisticRegression()
results = cross_val_score(model, X, y, cv=kfold)
#model.fit(X_train, Y_train)
#result = model.score(X_test, Y_test)
print("Accuracy: mean : %.3f%%, SD : %.3f%%") % (results.mean()*100.0, results.std()*100.0)
Esta técnica trabaja con k = len(X), con el objetivo de estimar con una mayor precisión el modelo para datos nuevos. Como desventaja, es computacionalmente costoso que la técnica cross validation y su varianza es alta.
%%time
from sklearn.cross_validation import LeaveOneOut
num_instances = len(X)
loocv = LeaveOneOut(n=num_instances)
model = LogisticRegression()
results = cross_val_score(model, X, y, cv=loocv)
#model.fit(X_train, Y_train)
#result = model.score(X_test, Y_test)
print("Accuracy: mean : %.3f%%, SD : %.3f%%") % (results.mean()*100.0, results.std()*100.0)
Esta técnica se usa cuando las clases no están balanceadas, es decir, cuando se tiene diferente cantidades de registros de cada clase.
%%time
import numpy as np
from sklearn.cross_validation import StratifiedShuffleSplit
X_array = np.array(X)
y_array = np.array(y)
sss = StratifiedShuffleSplit(y_array, n_iter=1, test_size=0.3, random_state=0)
for train_index, test_index in sss:
X_train, X_test = X_array[train_index], X_array[test_index]
y_train, y_test = y_array[train_index], y_array[test_index]
model = LogisticRegression()
model.fit(X_train, y_train)
result = model.score(X_test, y_test)
print("Accuracy: %.3f%%") % (result*100.0)
En esta parte se verá las diferentes métricas para evaluar la calidad de las predicciones de un modelo de ML. Para la evaluación se usará un k=10 en todas las datasets.
$$ \text{metric} = \frac{\text{# correct predictions}}{\text{# total predictions}}$$
Es recomendable cuando solo se tiene un número igual de observaciones de cada clase.
num_folds = 10
num_instances = len(X)
seed = 7
kfold = KFold(n=num_instances, n_folds=num_folds,
random_state=seed)
model = LogisticRegression()
scoring = 'accuracy'
results = cross_val_score(model, X, y,
cv=kfold, scoring=scoring)
print("Accuracy: mean : %.3f (%.3f)") % (results.mean()*100, results.std())
Es una métrica usada para clases que no están balanceadas, por ejemplo, en el dataset Iris tenemos 50 filas de cada especie las cuales se dice que esta balanceadas, pero en muchos otros problemas las clases estan desbalanceadas, por tanto, esta métrica permite saber específicamente cómo se comporta el modelo clase por clase.
from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report
model.fit(X_train, y_train)
predictions = model.predict(X_test)
cm = confusion_matrix(y_test, predictions)
print('Cantidad de valores de prueba: ', len(y_test))
df_cm = pd.DataFrame(cm, y.unique(),
y.unique())
plt.figure(figsize=(15,15))
ax = plt.axes()
_ = sns.set(font_scale=1.4)
_ = sns.heatmap(df_cm, annot=True,annot_kws={"size": 16}, ax=ax)
El Pipeline es una manera de juntar transformadores (p.e, Scaler, SelectKbest) y estimadores (ExtraTrees, Logistic Regression) en un solo flujo, de tal manera que cada eslabon del pipeline se ejecute secuencialmente hasta lograr el modelo predictivo. Por ejemplo, se puede poner en un flujo los Transformers: Scale, Normalization, etc, y en otro pipeline, transformers para features selections: univariate, pca, etc. y en otro pipeline poner estimadores: PCA, k-means, etc. Entonces con la función FeatureUnion, podemos unir esos tres pipelines para crear un pipeline total, que realiza todos esos pasos como uno solo. Esto ayuda a evitar la perdida de data y la legibilidad.
from sklearn.cross_validation import KFold
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import Normalizer
from sklearn.pipeline import Pipeline
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.pipeline import FeatureUnion
from sklearn.decomposition import PCA
from sklearn.feature_selection import SelectKBest
from sklearn.svm import SVC
transformers = []
transformers.append(( 'standardize' , StandardScaler()))
# # estimators.append(( 'normalizer' , Normalizer()))
# estimators.append(( 'lda' , LinearDiscriminantAnalysis()))
transf_union = FeatureUnion(transformers)
# model = Pipeline(estimators)
# create feature union
features = []
features.append(('transf_union', transf_union))
features.append(( 'pca', PCA(n_components=3)))
features.append(( 'select_best' , SelectKBest(k=3)))
feature_union = FeatureUnion(features)
# create pipeline
estimators = []
estimators.append(( 'feature_union' , feature_union))
estimators.append(( 'SVC' , SVC()))
# estimators.append(('svm' , SVC(kernel="linear")))
model = Pipeline(estimators)
# evaluate pipeline
num_folds = 10
num_instances = len(X)
seed = 7
kfold = KFold(n=num_instances, n_folds=num_folds, random_state=seed)
model.fit(X_train, y_train)
result = model.score(X_test, y_test)
print('Score:', result)
predictions = model.predict(X_test)
# results = cross_val_score(model, X, y, cv=kfold)
# print('Score:', results.mean())
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, predictions)
print('Cantidad de valores de prueba: ', len(y_test))
df_cm = pd.DataFrame(cm, y.unique(),
y.unique())
plt.figure(figsize=(15,15))
ax = plt.axes()
_ = sns.set(font_scale=1.4)
_ = sns.heatmap(df_cm, annot=True,annot_kws={"size": 16}, ax=ax)
import numpy as np
from sklearn.preprocessing import LabelEncoder
# encoder = LabelEncoder()
# y_test = encoder.fit_transform(y_test)
# predictions = encoder.fit_transform(predictions)
#Ploteando
y_test_l = list(y_test)
predictions = list(predictions)
plt.figure(figsize=(10,10))
plt.scatter(y_test_l, predictions, alpha=0.3, color='green', s=300)
plt.plot([min(y_test_l), max(y_test_l)], [min(y_test_l), max(y_test_l)],
'k--', color='darkgreen',lw=4,
label= 'X: Real value, Y: Predicted value. Score = '+str(result*100)+'%')
plt.xticks(tuple(y.unique()), tuple(y.unique()), rotation=25,
fontsize = 10)
plt.yticks(tuple(y.unique()), tuple(y.unique()), rotation=29,
fontsize = 10)
plt.legend(loc='best')
plt.grid(True)